package cn.xm.basic.service.impl;

import cn.xm.basic.config.BussinessException;
import cn.xm.basic.constant.BaseConstant;
import cn.xm.basic.dto.AccountLoginDto;
import cn.xm.basic.dto.WechatBinderDto;
import cn.xm.basic.dto.WechatLoginDto;
import cn.xm.basic.jwt.JwtUtils;
import cn.xm.basic.jwt.RsaUtils;
import cn.xm.basic.jwt.UserInfo;
import cn.xm.basic.service.LoginService;
import cn.xm.basic.util.AjaxResult;
import cn.xm.basic.util.HttpUtil;
import cn.xm.basic.util.MD5Utils;
import cn.xm.basic.util.StrUtils;
import cn.xm.system.domain.Menu;
import cn.xm.system.domain.Permission;
import cn.xm.system.mapper.MenuMapper;
import cn.xm.system.mapper.PermissionMapper;
import cn.xm.user.domain.Logininfo;
import cn.xm.user.domain.User;
import cn.xm.user.domain.Wxuser;
import cn.xm.user.dto.PhoneRegisterDto;
import cn.xm.user.mapper.LogininfoMapper;
import cn.xm.user.mapper.UserMapper;
import cn.xm.user.mapper.WxuserMapper;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.Pair;
import org.apache.poi.util.StringUtil;
import org.apache.xmlbeans.SchemaLocalAttribute;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.AutoConfigurationPackage;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

import java.security.PrivateKey;
import java.util.*;
import java.util.concurrent.TimeUnit;

@Service
public class LoginServiceImpl implements LoginService {
    @Autowired
    private LogininfoMapper logininfoMapper;
    @Autowired
    private RedisTemplate redisTemplate;
    @Autowired
    private WxuserMapper wxuserMapper;
    @Autowired
    private UserMapper userMapper;
    @Autowired
    private MenuMapper menuMapper;
    @Autowired
    private PermissionMapper permissionMapper;
    @Value("${jwt.rsa.pri}")
    private String privateRsaKey;
    /**
     * 手机号登录
     * @param dto
     * @return
     */
    @Override
    public Map<String, Object> accountLogin(AccountLoginDto dto) {
        // 校验参数非空
        // 校验账户是否正确
        Logininfo logininfo = logininfoMapper.findByUsername(dto.getUsername(),dto.getType());
        if (logininfo == null) {
            throw new BussinessException("账号或密码错误");
        }
        // 获取盐值
        String salt = logininfo.getSalt();
        // 校验密码是否正确
        String encrypByMd5 = MD5Utils.encrypByMd5(salt + dto.getPassword());
        if (StringUtils.isBlank(encrypByMd5) || !encrypByMd5.equals(logininfo.getPassword())) {
            throw new BussinessException("账号或密码错误");
        }
        return getMap(logininfo);
    }

    /**
     * 微信登录
     * @param dto
     * @return
     */
    @Override
    public AjaxResult wechatLogin(WechatLoginDto dto) {
        // 判断参数非空
        // 从对象中获取access_token和openid
        Pair<String, String> tokenAndOpenId = getTokenAndOpenId(dto.getCode());
        // 根据openid查询wxuser
        Wxuser wxuser = wxuserMapper.findByOpenId(tokenAndOpenId.getRight());
        // 数据库中存在该用户且已绑定，直接将数据库中信息返回前端让其登录
        if (wxuser != null && wxuser.getUserId() != null) {
            Logininfo logininfo = logininfoMapper.findByUserId(wxuser.getUserId());
            HashMap<String, Object> map = getMap(logininfo);
            return AjaxResult.me().success(map);
        }
        // 数据库中不存在该用户或没有绑定，需要跳转到绑定
        // 需要将openid和access_token返回给前端
        StringBuffer stringBuffer = new StringBuffer()
                .append("?openId=")
                .append(tokenAndOpenId.getRight())
                .append("&accessToken=")
                .append(tokenAndOpenId.getLeft());
        return AjaxResult.me().fail(stringBuffer);

    }

    /**
     * 微信绑定方法
     * @param dto
     */
    @Override
    public Map<String, Object> wechatBinder(WechatBinderDto dto) {
        // 验证参数非空
        // 检验验证码
        verifySmsCode(dto);
        // 访问第三个接口通过token和openid获取用户信息
        String url = String.format(BaseConstant.wxLoginCont.WX_LOGIN_USER_INFO, dto.getAccessToken(), dto.getOpenId());
        String httpGet = HttpUtil.httpGet(url);
        // 将json字符串转换为对象
        Wxuser wxuser = JSONObject.parseObject(httpGet, Wxuser.class);
        // 先查user表有没有信息
        User user = userMapper.findByPhone(dto.getPhone());
        Logininfo logininfo = new Logininfo();
        // 没有则存三张表，先存logininfo后存user
        if (user == null) {
            // 给user和logininfo初始化设置值
            user = initUser(dto);
            logininfo = initLogininfo(user);
            // 存
            logininfoMapper.add(logininfo);
            user.setLogininfoId(logininfo.getId());
            userMapper.add(user);
        } else {
            logininfo = logininfoMapper.findByUserId(user.getId());
        }
        // 有则只存wx_user表
        wxuser.setUserId(user.getId());
        wxuserMapper.add(wxuser);
        return getMap(logininfo);

    }

    private User initUser(WechatBinderDto dto){
        // 盐值
        String salt = StrUtils.getComplexRandomString(32);
        // 密码
        String password = salt + "1";
        String encryPwd = MD5Utils.encrypByMd5(password);
        return User
                .builder()
                .username(dto.getPhone())
                .phone(dto.getPhone())
                .salt(salt)
                .password(encryPwd)
                .state(1)
                .createtime(new Date())
                .build();
    }

    private Logininfo initLogininfo(User user){
        Logininfo logininfo = new Logininfo();
        BeanUtils.copyProperties(user,logininfo);
        // 类型为绑定
        logininfo.setType(1);
        return logininfo;
    }

    /**
     * 验证码是否正确
     * @param dto
     */
    private void verifySmsCode(WechatBinderDto dto) {
        String smsCode = (String)redisTemplate.opsForValue().get(String.format(BaseConstant.verifyCodeCons.BINDER_SMSCODE_PHOEN, dto.getPhone()));
        if (StringUtils.isBlank(smsCode)) {
            throw new BussinessException("验证码错误");
        }
        if (!smsCode.equals(dto.getVerifyCode())) {
            throw new BussinessException("验证码错误");
        }
    }

    private Pair<String,String> getTokenAndOpenId(String code) {
        // 获取到微信token的url
        String tokenUrl = String.format(BaseConstant.wxLoginCont.WX_LOGIN_TOKEN_URL, BaseConstant.wxLoginCont.WX_LOGIN_APPID,
                BaseConstant.wxLoginCont.WX_LOGIN_SECRET, code);
        String httpGet = HttpUtil.httpGet(tokenUrl);
        // 转换为json对象
        JSONObject jsonObject = JSONObject.parseObject(httpGet);
        // 从对象中获取access_token和openid
        String access_token = jsonObject.getString("access_token");
        String openid = jsonObject.getString("openid");
        return Pair.of(access_token,openid);

    }

    /**
     * 将token和logininfo存入redis并返回为map
     * @param logininfo
     * @return
     */
    private HashMap<String, Object> getMap(Logininfo logininfo) {
        // 1 获取token
        PrivateKey privateKey = RsaUtils.getPrivateKey(JwtUtils.class.getClassLoader().getResource(privateRsaKey).getFile());
        UserInfo user = new UserInfo();
        user.setLogininfo(logininfo);

        // 返回参数（token和logininfo）给前端
        HashMap<String, Object> map = new HashMap<>();
        // 如果是后台来的请求,需要查询到改用户的权限并加到jwt的token中
        if (logininfo.getType() == 0) {
            List<Menu> menus = menuMapper.findByLogininfoId(logininfo.getId());
            List<String> permissions = permissionMapper.findByLogininfoId(logininfo.getId());
            // 将菜单和权限放在token中
            user.setMenu(menus);
            user.setPermission(permissions);
            map.put("menus",menus);
            map.put("permissions",permissions);
        }
        // 将jwt中的头，载荷和校验信息都填上
        String token =JwtUtils.generateTokenExpireInMinutes(user, privateKey, 1800);
        // 前台来的请求不用判断权限直接放行
        // 前后台的最终都要返回token给前端
        map.put("token",token);
        // 清空uuid关键信息
        logininfo.setPhone("");
        logininfo.setPassword("");
        logininfo.setSalt("");
        map.put("logininfo", logininfo);
        return map;
    }

}
